To see in an example of polling for status, see function inWait0() under "Asynchronous I/O Example". This function is used when the aiocb is initialized with SIGEV_NONE, meaning that no notification is to be returned at the completion of the operation. The function waits for an asynchronous operation to complete using a loop in the general form shown in Example 8-2.
Example 8-2 : Polling for Asynchronous Completion
int waitForEndOfAsyncOp(aiocb *pab) { while (EINPROGRESS == (ret = aio_error(pab))) sginap(0); return ret; }The function result is the final return code from the read, write, or sync operation that was started. Under the Frame Scheduler, the call to sginap() would be replaced with a call to frs_yield().
These choices give you a wide variety of design options. Your program can
Tip: When operating under the Frame Scheduler, a handler or callback function can simply set a flag. An activity process can test the flag in each minor frame, calling frs_yield() immediately if the flag is not set.
aio_sigevent.sigev_notify | Set to SIGEV_SIGNAL. |
aio_sigevent.sigev_signo | The number of the signal. This should be one of the POSIX real-time signal numbers (see "Signals"). |
aio_sigevent.sigev_value | A value to be passed to the signal handler. This can be used to inform the signal handler of which I/O operation has completed; for example, it could be the address of the aiocb. |
When you set up a signal handler for asynchronous completion, do so using sigaction() and specify the SA_SIGINFO flag (see the sigaction(2) reference page). This has two benefits: any new completion signal that arrives while the first is being handled is queued; and the aio_sigev.sigev_value word is passed to the handler in a siginfo structure.
aio_sigevent.sigev_notify | Set to SIGEV_CALLBACK. |
aio_sigevent.sigev_func | The address of the callback function. Its prototype must be void functionName(union sigval); |
aio_sigevent.sigev_value | A word to be passed to the callback function. This can be used to inform the function of which I/O operation has completed; for example, it could be the address of the aiocb. |
The callback function is invoked from the asynchronous process when the read(), write() or fsync() operation finishes. This notification method has the lowest overhead and shortest latency, but it requires careful design to avoid race conditions in the use of shared variables.
The asynchronous processes are created with sproc(), so they share the address space of the process that initialized asynchronous I/O. They typically execute in a different CPU from the real-time processes using that address space. Since the callback function could be entered at any time, it must coordinate its use of shared data structures. This is a good place to use a lock (see "Locks"). Locks have very low overhead in cases such as this, where there is likely to be little contention for the use of the lock.
Tip: You can call aio_read() or aio_write() from within a callback function or within a signal handler. This lets you start another operation with the least delay. The code in Example 8-3 demonstrates a hypothetical set of subroutines to schedule asynchronous reads and writes using a single aiocb. The principle functions and global variables it uses are:
pendingIO | An array of records, each holding one request for an I/O operation. |
dontTouchThatStuff | A lock used to gain exclusive use of pendingIO. |
scheduleRead() | A function that accepts a request to read some amount of data, from a specified file descriptor, at a specified file offset. It places the request in pendingIO and then, if no asynchronous operation is under way, initiates it. |
yeahWeFinishedOne() | The callback function that is entered when an asynchronous operation completes. If any more operations are pending, it initiates one. |
initiatePending() | A function that initiates one selected pending operation. It prepares the aiocb structure, including the specification of yeahWeFinishedOne() as the callback function. The lock dontTouchThatStuff must be held before this function is called. |
Note: The code in Example 8-3 is not intended to be realistic and is not recommended as a model. In order to demonstrate the use of callback functions and the aiocb, it essentially duplicates work that could be done by the lio_listio() feature of asynchronous I/O.
Example 8-3 : Set of Functions to Schedule Asynchronous I/O
#define _ABI_SOURCE #include <signal.h> #include <aio.h> #include <ulocks.h> #define MAX_PENDING 10 #define STATUS_EMPTY 0 #define STATUS_ACTIVE 1 #define STATUS_PENDING 2 static struct onePendingIO { int status; int theFile; void *theData; off_t theSize; off_t theSeek; int readNotWrite; } pendingIO[MAX_PENDING]; static unsigned numPending; static struct aiocb theAiocb; static ulock_t dontTouchThatStuff; static unsigned scanner; static void initiatePending(int P); static void yeahWeFinishedOne(union sigval S) { ussetlock(dontTouchThatStuff); pendingIO[S.sival_int].status = STATUS_EMPTY; if (numPending) { while (pendingIO[scanner].status != STATUS_PENDING) { if (++scanner >= MAX_PENDING) scanner = 0; } initiatePending(scanner); } usunsetlock(dontTouchThatStuff); } static void initiatePending(int P) /* lock must be held on entry */ { theAiocb.aio_fildes = pendingIO[P].theFile; theAiocb.aio_buf = pendingIO[P].theData; theAiocb.aio_nbytes = pendingIO[P].theSize; theAiocb.aio_offset = pendingIO[P].theSeek; theAiocb.aio_sigevent.sigev_notify = SIGEV_CALLBACK; theAiocb.aio_sigevent.sigev_func = yeahWeFinishedOne; theAiocb.aio_sigevent.sigev_value.sival_int = P; if (pendingIO[P].readNotWrite) aio_read(&theAiocb); else aio_write(&theAiocb); pendingIO[P].status = STATUS_ACTIVE; --numPending; } /*public*/ int scheduleRead( int FD, void *pdata, off_t len, off_t pos ) { int j; if (numPending >= MAX_PENDING) likeTotallyFreakOut(); ussetlock(dontTouchThatStuff); for(j=0; pendingIO[j].status != STATUS_EMPTY; ++j) ; pendingIO[j].theFile = FD; pendingIO[j].theData = pdata; pendingIO[j].theSize = len; pendingIO[j].theSeek = pos; pendingIO[j].readNotWrite = 1; pendingIO[j].status = STATUS_PENDING; if (1 == ++numPending) initiatePending(j); usunsetlock(dontTouchThatStuff); }